package com.spzx.cart.service.impl;

import com.spzx.cart.api.domain.CartInfo;
import com.spzx.cart.service.CartService;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.product.api.RemoteProductService;
import com.spzx.product.api.domain.ProductSkuVo;
import com.spzx.product.api.domain.SkuPriceVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Description:
 *
 * @author Yanxinhao on 2024/12/25
 */
@Service
public class CartServiceImpl implements CartService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RemoteProductService productService;

    /**
     * 添加商品到购物车
     *   - 具体思路：
     *     1.使用redis的hash数据结构来存储用户的购物车列表
     *       redis的hash数据结构可以分为3个部分，分别是：键(key) 字段(filed) 值(value)
     *       我们将当前用户的id作为键，然后以商品的skuId作为字段，以购物车商品对象CartInfo作为值
     *     2.进入业务逻辑中，优先判断当前用户的购物车中是否存在该商品，若存在，则让数量加一即可
     *     3.判断用户单件商品不得购买超过99件
     *     4.判断用户购物车中商品的种类不得超过50种
     *     5.将购物车数据组装好后，以hash数据结构添加进redis数据库种
     * @param skuId 商品规格id
     * @param skuNum 商品数量
     */
    @Override
    public void addToCart(Long skuId, Integer skuNum) {
        //1.优先获取我们存入redis数据库中的购物车数据中的键(Key) 自定义形式: "user:cart:userId"
        String cartKey = getCartKey(SecurityUtils.getUserId());
        //2.去判断我们的购物车中是否已经有该商品
        String hashKey = skuId.toString(); //redis中的field是字符串类型的，我们要做类型转换来匹配
        //使用boundHashOps(Key)的方式创建一个专门服务于对应Key值的一个redis中的Hash操作对象
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
//        Boolean isExist = redisTemplate.opsForHash().hasKey(cartKey,hashKey);
        Boolean isExist = hashOps.hasKey(hashKey);
        //2.1若存在，那么就执行让当前hashKey为skuId的商品的值进行数量累加
        if (isExist){
            //获取对应field的value
            CartInfo cartInfo =hashOps.get(hashKey);
            //2.2对数量进行累加,并且判断其是否超过99的数量上限
            int maxNum = 99;
            int finalNum = cartInfo.getSkuNum() + skuNum;
            cartInfo.setSkuNum( finalNum > maxNum ? maxNum : finalNum);
            //2.3将修正后的value值覆盖原先值
            hashOps.put(hashKey,cartInfo);
        } else {
            //3.若当前用户的购物车不存在这个商品，那么就执行添加购物车逻辑
            //3.1但是在添加商品到购物车之前，需要判断用户购物车中商品的种类不得超过50种
            Long cartSize = hashOps.size();
            if (++cartSize > 5){
                throw new RuntimeException("购物车中商品种类不得超过50种");
            } else {
                //3.2根据skuId，组装出一个CartInfo对象，装进redis中
                // 因为当前的CartInfo对象有相当多的属性跟ProductSku对象高度重合
                // 因此我们可以调用OpenFeign的远程调用，我们在spzx-cart调用spzx-product模块中的getProductSku方法
                // 我们已经将ProductSkuController中的getProductSku方法封装成一个FeignClient于spzx-api-product当中的RemoteProductService中
                // 我们将RemoteProductService注入到当前类中，然后调用getProductSku方法
                R<ProductSkuVo> productSkuResult = productService.getProductSku(skuId, SecurityConstants.INNER);
                //3.3 先去判断远程调用返回的商品sku对象是否有误
                if (R.FAIL == productSkuResult.getCode()){
                    throw new ServiceException(productSkuResult.getMsg());
                } else {
                    //3.4若没有发生错误，那么就组装对象并返回
                    ProductSkuVo productSku = productSkuResult.getData();
                    CartInfo cartInfo = new CartInfo();
                    cartInfo.setUserId(SecurityUtils.getUserId());
                    cartInfo.setSkuNum(skuNum);
                    cartInfo.setSkuId(skuId);
                    cartInfo.setCartPrice(productSku.getSalePrice());
                    cartInfo.setSkuPrice(productSku.getSalePrice());
                    cartInfo.setThumbImg(productSku.getThumbImg());
                    cartInfo.setSkuName(productSku.getSkuName());
                    //默认给“是否选择”设置为选中
                    cartInfo.setIsChecked(1);
                    //添加到购物车前，给商品添加一个添加进购物车的时间属性，即创建时间，用于我们后面给购物车排序
                    cartInfo.setCreateTime(new Date());
                    //将设置好的值直接干到redis里
                    hashOps.put(hashKey,cartInfo);
                }
            }
        }
    }

    /**
     * 查询用户的购物车商品列表
     * @return
     */
    @Override
    public List<CartInfo> getCartList() {
        //获取键值
        String cartKey = getCartKey(SecurityUtils.getUserId());
        //获取对应键值的hash操作对象
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        //获取当前用户名下的所有购物车商品列表
        List<CartInfo> cartInfoList = hashOps.values();
        //若当前用户的购物车列表为空，那么就返回一个空的集合
        if (CollectionUtils.isEmpty(cartInfoList)){
            return new ArrayList<>();
        } else {
            //若当前用户的购物车有商品，我们将商品集合以更新的时间降序排序
            //使用流式操作，给集合以添加进购物车的时间进行降序排序
            //.sorted()操作当中可以传入一个Comparator(比较器),Comparator是一个函数式接口，我们可以用lambda
            List<CartInfo> resultList = cartInfoList.stream().sorted((o1, o2) -> {
                return o2.getCreateTime().compareTo(o1.getCreateTime());
            }).toList();
            return resultList;
        }
    }

    /**
     * 删除对应id的购物车商品
     * @param skuId
     */
    @Override
    public void deleteCart(Long skuId) {
        String cartKey = getCartKey(SecurityUtils.getUserId());
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        //先做健壮性测试，判断传来的skuId是否存在
        String hashKey = skuId.toString();
        CartInfo cartInfo = hashOps.get(hashKey);
        if (cartInfo == null){
            throw new RuntimeException("当前用户购物车中没有此商品");
        } else {
            //执行删除
            hashOps.delete(hashKey);
        }
    }

    /**
     * 修改指定id的购物车商品的选中状态
     * @param skuId
     * @param isChecked
     */
    @Override
    public void checkCart(Long skuId, Integer isChecked) {
        //保证健壮性，优先判断传来的isCheck 是否为1或者0
        if (isChecked != 0 && isChecked != 1){
            throw new ServiceException("非法参数");
        }
        String cartKey = getCartKey(SecurityUtils.getUserId());
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        //获取对应id的商品并且更新isCheck值
        String hashKey = skuId.toString();
        CartInfo cartInfo = hashOps.get(hashKey);
        cartInfo.setIsChecked(isChecked);
        //保存结果
        hashOps.put(hashKey,cartInfo);
    }

    /**
     * 购物车商品  全选/取消全选
     * @param isChecked
     */
    @Override
    public void allCheckCart(Integer isChecked) {
        //获取当前用户存储购物车数据的hash数据结构中对应的键值
        String cartKey = getCartKey(SecurityUtils.getUserId());
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        //对该用户底下的所有购物车商品进行遍历，获取每一个商品，并且修改isChecked值，然后覆盖回去
        hashOps.values().forEach(info -> {
            info.setIsChecked(isChecked);
            hashOps.put(info.getSkuId().toString(),info);
        });
    }

    /**
     * 清空购物车的商品数据
     */
    @Override
    public void clearCart() {
        //获取当前用户存储购物车数据的hash数据结构中对应的键值
        String cartKey = getCartKey(SecurityUtils.getUserId());
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        hashOps.delete(cartKey);
    }

    /**
     * 供给给其他微服务远程调用的接口
     * 获取用户购物车选中商品列表
     * @return 被选中的商品列表
     */
    @Override
    public List<CartInfo> getCartCheckedList() {
        //获取键名
        String cartKey = getCartKey(SecurityUtils.getUserId());
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        List<CartInfo> cartInfoList = hashOps.values();
        /*
        //保证健壮性，先判断商品列表是否为空
        if (CollectionUtils.isEmpty(cartInfoList)){
            //若购物车里没有商品，那么就返回一个空的集合
            return new ArrayList<>();
        }
        */
       return cartInfoList
               .stream()
               .filter(cartInfo -> cartInfo.getIsChecked() == 1)
               .collect(Collectors.toList());
    }

    /**
     * 供给给其他微服务远程调用的接口
     * 更新用户购物车商品列表中的价格
     * 保证用户提交订单，支付的时候，支付的商品价格是最新的更新价格，避免价格被篡改
     * @param userId
     * @return
     */
    @Override
    public Boolean updateCartPrice(Long userId) {
        String cartKey = getCartKey(userId);
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        //获取对应用户名下的购物车商品列表
        List<CartInfo> cartInfoList = hashOps.values();
        //健壮性判断，只有购物车里面有东西的时候才执行下面的代码
        if (!CollectionUtils.isEmpty(cartInfoList)){
            for (CartInfo cartInfo : cartInfoList) {
                //我们只需要去保证支付的商品的价格是最新的，也就是状态为被选中的，商品的价格才值得我们去保证最新
                if (cartInfo.getIsChecked().intValue() == 1){
                    //调用spzx-product模块中的ProductSkuController中的方法，去获取最新价格
                    R<SkuPriceVo> priceResult = productService.getSkuPrice(cartInfo.getSkuId(), SecurityConstants.INNER);
                    //判断返回结果是否有问题
                    if (R.FAIL == priceResult.getCode()){
                        throw new ServiceException("获取商品最新价格失败");
                    }
                    SkuPriceVo priceData = priceResult.getData();
                    //更新cartInfo的价格为最新价格，并且覆盖原先的数据
                    cartInfo.setSkuPrice(priceData.getSalePrice());
//                    cartInfo.setCartPrice(priceData.getSalePrice());
                    hashOps.put(cartInfo.getSkuId().toString(),cartInfo);
                }
            }
        }
        return true;
    }

    /**
     * 用户完成支付后，删除购物车中选中的商品
     * @param userId
     * @return
     */
    @Override
    public Boolean deleteCartCheckedList(Long userId) {
        String cartKey = getCartKey(userId);
        BoundHashOperations<String,String,CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        //获取购物车商品列表
        List<CartInfo> cartInfoList = hashOps.values();
        //遍历集合列表，删除所有状态为被选中的商品
        if (!CollectionUtils.isEmpty(cartInfoList)){
            for (CartInfo cartInfo : cartInfoList) {
                if (cartInfo.getIsChecked() == 1){
                    hashOps.delete(cartInfo.getSkuId().toString());
                }
            }
        }
        return true;
    }

    /**
     * 用户的购物车商品信息存储于redis的hash数据结构中
     * 此方法用于生成固定的hash结构的键值
     * @param userId
     * @return
     */
    private String getCartKey(Long userId) {
        return "user:cart:" + userId;
    }
}